SSEG SEGMENT STACK    ;程序段SSEG
    STK DB 20 DUP(0)    ;定义字节变量STK，分配20个字节的空间，初值均为0
SSEG ENDS

DSEG SEGMENT      ;程序段DSEG
    MP DB 225 DUP(0)    ;定义字节变量MP，分配225个字节的空间，初值均为0
    I DB 0    ;横坐标   字节变量I，初值为0
    J DB 0    ;纵坐标   字节变量J，初值为0
    TI DB 0;    字节变量TI，初值为0
    TJ DB 0;暂用变量    字节变量TJ，初值为0
    CNTW DW 0;基址偏移的16位量表示  单词（2个字节）变量CNTW，初值为0
    CNT DB 0;基址偏移的8位量表示    单字节变量DB，初值为0
    FINISH DB 0    ;棋局是否完成    字节变量FINISH，初值为0
    COUNT DB 1    ;连线上的棋子个数     字节变量COUNT，初值为1
    OVERFLOW DB 0    ;棋盘是否满了 下满225次未有人胜利即后宣布和棋  字节变量OVERFLOW，初值为0
    NOW DB 0    ;    字节变量NOW，初值为0

    ;ODH,OAH组合执行为换行，'$'结束字符串
    STR1 DB 'Game start,either side can win by reaching five pieces on a line.',0DH,0AH,'$'
    STR2 DB '    0  1  2  3  4  5  6  7  8  9 10 11 12 13 14',0DH,0AH,'$'
    STR3 DB 0DH,0AH,'$'
;各个游戏提示语句
    STR4 DB 'White round, please enter two numbers, separated by spaces,end withs #',0DH,0AH,'$'    
    STR5 DB 'Black round, please enter two numbers, separated by spaces,end withs #',0DH,0AH,'$'
    STR6 DB 'The data is wrong, please re-enter it',0DH,0AH,'$'
    STR7 DB 'White win.',0DH,0AH,'$'
    STR8 DB 'Black win.',0DH,0AH,'$'
    STR9 DB 'Game ends,draw.',0DH,0AH,'$'
DSEG ENDS

CSEG SEGMENT  ;程序段CSEG
    ASSUME CS:CSEG,DS:DSEG    ;CS对应CSEG的内容，DS对应DSEG的内容
    ASSUME SS:SSEG            ;SS对应SSEG的内容

MAIN PROC ;主程序
START:    
    MOV AX,DSEG         ;AX=DSEG中的STR1
    MOV DS,AX           ;数据段寄存器DS=AX
    MOV AX,SSEG         ;AX=程序段SSEG里的变量STK
    MOV SS,AX           ;堆栈段寄存器SS=AX
    MOV SP,LENGTH STK   ;堆栈指针寄存器SP=STK中的数据项数目（20）
    MOV DX,OFFSET STR1  ;数据寄存器DX=变量STR1(ODH,OAH组合执行为换行)
    ;DX=STR1='Game start,either side can win by reaching five pieces on a line.',0DH,0AH,'$'
   
    MOV AH,9            ;AH=9（0000 1001）
    INT 21h             ;中断指令，显示游戏开始  AH=09时输出字符串DX
;这是最开始的外圈循环  白方先手继而黑方

IN_WHITE:           ;白方落子循环 
                    ;此处黑白落子逻辑判断代码有大部分相似,但是避免跳转逻辑过于复杂因此黑白落子判断分开执行

;显示棋盘      
    CALL SHOW    ;调用PROC子程序SHOW显示棋盘
;初始化
    MOV TI,0                ;TI=0
    MOV TJ,0                ;TJ=0  多一层防止数据错误
    MOV DX,OFFSET STR4      ;白方回合提示，(0DH,0AH组合执行为换行,'$'结束字符串)
    ;DX=STR4='White round, please enter two numbers, separated by spaces,end withs #',0DH,0AH,'$'
    MOV AH,9                ;AH=9
    INT 21h                 ;中断，AH=9时输出字符串DX

;读取第一个数（行数m）的第一位，第一个数是（m，n）的行数m
    MOV AH, 1               ;AH=1
    INT 21H                 ;中断，AH=1时读取键盘输入，赋值给AL
    
    CMP AL,30H              ;比较AL和0（ASCII码30H）
    JB W_NOT                ;如果AL<0,跳转到错误报告W_NOT
    
    CMP AL,39H              ;比较AL和9(ASCII码39H)
    JA W_NOT                ;如果AL>9，跳转到错误报告W_NOT
    
    JMP NEXTW    ;第一个数的第一位输入无误，跳转到NEXTW比较下一位

W_NOT:  ;输入错误时报告
;输出换行，0DH和0AH一起执行表示换行
    MOV DL,0DH          ;DL=0DH（回车符）
    MOV AH,2            ;AH=2            
    INT 21H             ;中断，AH=2时候输出DL
    MOV DL,0AH          ;DL=0AH（换行符）
    MOV AH,2            ;AH=2
    INT 21H             ;中断，AH=2时输出DL

;报告错误信息
    MOV DX,OFFSET STR6  ;DX=错误信息
    ;DX=STR6='The data is wrong, please re-enter it',0DH,0AH,'$'
    MOV AH,9            ;AH=9
    INT 21h             ;中断,AH=9时输出字符串DX（反馈错误）

    JMP IN_WHITE        ;返回白方落子循环处，让用户重新输入

NEXTW:  ;判断第一个数（行数）的第二位，对应行m (m,n)
;初始化
    SUB AL, 30H         ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TI,AL           ;TI暂存第一个数字的第一位

;读取第一个数的第二位
    MOV AH, 1           ;AH=1
    INT 21H             ;中断，AH=1时读取键盘输入
    
    CMP AL,20H          ;比较AL和空格（ASCII码20H）
    JE NEXTWW           ;AL=20H（空格），说明第一个数是一位数，跳转到NEXTWW判断第二个数

;AL为两位数才执行以下操作
    CMP AL,30H          ;比较AL和0（ASCII码30H）    （0<AL<14）
    JB W_NOT            ;AL<0时跳转到错误报告
    
    CMP AL,34H          ;比较AL和4（ASCII码34）      (0<AL<14)
    JA W_NOT            ;AL>4的时候跳转到错误报告
    
    CMP TI,1            ;比较TI和1（第一个数的第一位）
    JA W_NOT            ;TI>1时即第一个数>=20,超过14，跳转到错误报告，
    MOV TI,10           ;TI=10
    SUB AL, 30H         ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TJ,AL           ;TJ=AL，暂存第一个数的第二位

NEXTWW:     ;保存行数字并在判断第二个数之前初始化
;保存行数字在I中
    MOV BL,TI   ;TI赋给BL（第一个数是一位数，则为该数，是二位数则是10）
    ADD BL,TJ   ;BL=BL+TJ（加上第一个数的第二位）（第一个数为一位数则是空格，否则是二位数的个位）
    MOV I,BL    ;行数I=BL=第一个数
;初始化,防止数据遗留出问题    
    MOV TI,0    ;0赋给TI
    MOV TJ,0    ;0赋给TJ

SEC:    ;判断第二个数正确与否，对应列n (m,n)
;读取第二个数字的第一位
    MOV AH, 1   ;AH=1
    INT 21H     ;中断，AH=1时读取键盘输入

    CMP AL,20H  ;AL和空格比较（ASCII码20H）
    JE SEC      ;AL=空格（20H），循环SEC再读取一次

    CMP AL,30H  ;AL和0比较（ASCII码30H）
    JB W_NOT    ;AL<0时，跳转到错误报告W_NOT
    CMP AL,39H  ;AL和9比较（ASCII码39H）

follow:    
    JA W_NOT        ;AL>9时，跳转到错误报告W_NOT
    SUB AL, 30H     ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TI,AL       ;TI暂存第二个数的第一位

;读取第二个数的第二位：十位数的个位或个位数的#    
    MOV AH, 1       ;AH=1
    INT 21H         ;中断，AH=1时读取键盘输入

    CMP AL,23H      ;比较AL和'#'（ASCII码23H）
    JE ENDWW        ;AL='#'时跳转到ENDWW，说明第二个数是一位数

;第二个数是二位数才执行以下操作
    CMP AL,30H      ;AL和0比较（ASCII码30H）
    JB W_NOT        ;AL<0时，跳转到错误报告W_NOT

    CMP AL,34H      ;AL和4比较（ASCII码34H）
    JA W_NOT        ;AL>4时，跳转到错误报告W_NOT

    CMP TI,1        ;TI和1比较（第二位数的第一位）
    JA follow       ;JA>1时说明AL>20，即超出14，输入错误，循环执行follow

    MOV TI,10       ;TI=10，转化成10便于操作，因为只有10-14所以十位只有这个
    SUB AL, 30H     ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TJ,AL       ;TJ暂存第二个数的第二位
    
ENDWW:

;换行，0DH和0AH组合输出为换行
    MOV DL,0DH      ;DL=回车符（ACSII码0DH）
    MOV AH,2        ;AH=2
    INT 21H         ;中断，AH=2时输出DL
    MOV DL,0AH      ;DL=换行符（ASCII码0AH)
    MOV AH,2        ;AH=2
    INT 21H         ;中断，AH=2时输出DL

;保存列数字在J中
    MOV BL,TI       ;TI赋给BL（第二个数是一位数，则为该数，是二位数则是10）
    ADD BL,TJ       ;BL=BL+TJ（加上第二个数的第二位）（第二个数为一位数则是#，否则是二位数的个位）
    MOV J,BL        ;列数J=BL=第二个数,保留列数据

;初始化,防止数据遗留出问题    
    MOV TI,0        ;TI=0
    MOV TJ,0        ;TJ=0

;获得相对MP的8位/16位的偏移量:行数*15+列数才是当前元素的相对于MP的偏移量
    MOV AL,I                ;AL=I   
    MOV CL,15               ;CL=15
    MUL CL                  ;AL=AL*CL=AL*15 行数*15

    MOV CNTW,AX             ;CNTW=AX
    MOV DL,BYTE PTR CNTW    ;DL=CNTW
    ADD DL,J                ;DL=DL+J    行数*15+列数

    MOV CNT,DL              ;CNT=DL,保留相对于MP的偏移量 为byte
    MOV AX,WORD PTR CNT     ;AX=CNT
    MOV CNTW,AX             ;CNTW=AX，保留相对于MP的偏移量 为Word
    MOV BX,OFFSET MP        ;BX=MP的偏移量
    ADD BX,CNTW             ;BX=BX+CNTW
    MOV AL,[BX]             ;取到MP当前玩家准备落子的坐标

    CMP AL,0                ;AL和0比较，确认此处是否没有落子
    JNE follow              ;AL不等于0表示此处有落子 该次落子失败,需要重新选择落子处，跳转到follow

;成功落子,改变MP的坐标数据
    ADD OVERFLOW,1          ;棋盘中棋子数量+1
    MOV BYTE PTR[BX],27H    ;以W代表白棋（ASCII码57H）  B代表黑棋
                            ;因为棋盘输出时每个数字自动加30H转为字符，所以此处的W的ASCII码57H先减去30H
;调用判断获胜与否的子程序WIN    
    CALL WIN           

    CMP FINISH,1            ;比较FINISH和1
    JE EXIT_TEMP            ;FINISH=1，获胜条件已达成   跳转到EXIT_TEMP结束程序进行第一遍确认

IN_BLACK:        ;黑方落子循环

;显示棋盘      
    CALL SHOW    ;调用PROC子程序SHOW显示棋盘
;初始化
    MOV TI,0                ;TI=0
    MOV TJ,0                ;TJ=0  多一层防止数据错误
    MOV DX,OFFSET STR5      ;黑方回合提示，(0DH,0AH组合执行为换行，'$'结束字符串)
    ;DX=STR5='Black round, please enter two numbers, separated by spaces,end withs #',0DH,0AH,'$'
    MOV AH,9                ;AH=9
    INT 21h                 ;中断，AH=9时输出字符串DX

;读取第一个数（行数m）的第一位，第一个数是（m，n）的行数m
    MOV AH, 1               ;AH=1
    INT 21H                 ;中断，AH=1时读取键盘输入，赋值给AL
    
    CMP AL,30H              ;比较AL和0（ASCII码30H）
    JB B_NOT                ;如果AL<0,跳转到错误报告B_NOT
    
    CMP AL,39H              ;比较AL和9(ASCII码39H)
    JA B_NOT                ;如果AL>9，跳转到错误报告B_NOT
    
    JMP NEXTB    ;第一个数的第一位输入无误，跳转到NEXTB比较下一位

B_NOT:  ;报告错误信息
;输出换行，0DH和0AH一起执行表示换行
    MOV DL,0DH          ;DL=0DH（回车符）
    MOV AH,2            ;AH=2            
    INT 21H             ;中断，AH=2时候输出DL
    MOV DL,0AH          ;DL=0AH（换行符）
    MOV AH,2            ;AH=2
    INT 21H             ;中断，AH=2时输出DL

;报告错误信息
    MOV DX,OFFSET STR6  ;DX=错误信息
    ;DX=STR6='The data is wrong, please re-enter it',0DH,0AH,'$'
    MOV AH,9            ;AH=9
    INT 21h             ;中断,AH=9时输出字符串DX（反馈错误）

    JMP IN_BLACK        ;返回黑方落子循环处，让用户重新输入

;结束程序（第一遍确认）
EXIT_TEMP:
    CMP FINISH,1        ;比较FINISH和1
    JE EXIT_TEMPP       ;FINISH=1,获胜条件已达成，跳转到结束程序EXIT_TEMPP进行第二遍确认

NEXTB:  ;判断第一个数（行数）的第二位，对应行m (m,n)
;初始化
    SUB AL, 30H         ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TI,AL           ;TI暂存第一个数字的第一位

;读取第一个数的第二位
    MOV AH, 1           ;AH=1
    INT 21H             ;中断，AH=1时读取键盘输入
    
    CMP AL,20H          ;比较AL和空格（ASCII码20H）
    JE NEXTBB           ;AL=20H（空格），说明第一个数是一位数，跳转到NEXTBB判断第二个数

;AL为两位数才执行以下操作
    CMP AL,30H          ;比较AL和0（ASCII码30H）    （0<AL<14）
    JB B_NOT            ;AL<0时跳转到错误报告
    
    CMP AL,34H          ;比较AL和4（ASCII码34）      (0<AL<14)
    JA B_NOT            ;AL>4的时候跳转到错误报告
    
    CMP TI,1            ;比较TI和1（第一个数的第一位）
    JA B_NOT            ;TI>1时即第一个数>=20,超过14，跳转到错误报告，
    MOV TI,10           ;TI=10
    SUB AL, 30H         ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TJ,AL           ;TJ=AL，暂存第一个数的第二位

NEXTBB:     ;保存行数字并在判断第二个数之前初始化
;保存行数字在I中
    MOV BL,TI   ;TI赋给BL（第一个数是一位数，则为该数，是二位数则是10）
    ADD BL,TJ   ;BL=BL+TJ（加上第一个数的第二位）（第一个数为一位数则是空格，否则是二位数的个位）
    MOV I,BL    ;行数I=BL=第一个数
;初始化,防止数据遗留出问题    
    MOV TI,0    ;0赋给TI
    MOV TJ,0    ;0赋给TJ

THR:    ;判断第二个数正确与否，对应列n (m,n)
;读取第二个数字的第一位
    MOV AH, 1   ;AH=1
    INT 21H     ;中断，AH=1时读取键盘输入

    CMP AL,20H  ;AL和空格比较（ASCII码20H）
    JE THR      ;AL=空格（20H），循环THR再读取一次

    CMP AL,30H  ;AL和0比较（ASCII码30H）
    JB B_NOT    ;AL<0时，跳转到错误报告B_NOT
    CMP AL,39H  ;AL和9比较（ASCII码39H）

folloB:
    JA B_NOT        ;AL>9时，跳转到错误报告B_NOT
    SUB AL, 30H     ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TI,AL       ;TI暂存第二个数的第一位

;读取第二个数的第二位：十位数的个位或个位数的#    
    MOV AH, 1       ;AH=1
    INT 21H         ;中断，AH=1时读取键盘输入

    CMP AL,23H      ;比较AL和'#'（ASCII码23H）
    JE ENDBB        ;AL='#'时跳转到ENDBB，说明第二个数是一位数

;第二个数是二位数才执行以下操作
    CMP AL,30H      ;AL和0比较（ASCII码30H）
    JB B_NOT        ;AL<0时，跳转到错误报告B_NOT

    CMP AL,34H      ;AL和4比较（ASCII码34H）
    JA B_NOT        ;AL>4时，跳转到错误报告B_NOT

    CMP TI,1        ;TI和1比较（第二位数的第一位）
    JA folloB       ;JA>1时说明AL>20，即超出14，输入错误，循环执行folloB

    MOV TI,10       ;TI=10，转化成10便于操作，因为只有10-14所以十位只有这个
    SUB AL, 30H     ;输入的结果默认放在AL，减去30H是为了把字符转换成数字
    MOV TJ,AL       ;TJ暂存第二个数的第二位
    

EXIT_TEMPP:     ;结束程序（第二遍确认）
    CMP FINISH,1    ;比较FINISH和1
    JE EXIT         ;FINISH=1，获胜条件已达成，跳转到结束程序EXIT

ENDBB:

;换行，0DH和0AH组合输出为换行
    MOV DL,0DH      ;DL=回车符（ACSII码0DH）
    MOV AH,2        ;AH=2
    INT 21H         ;中断，AH=2时输出DL
    MOV DL,0AH      ;DL=换行符（ASCII码0AH)
    MOV AH,2        ;AH=2
    INT 21H         ;中断，AH=2时输出DL

;保存列数字在J中
    MOV BL,TI       ;TI赋给BL（第二个数是一位数，则为该数，是二位数则是10）
    ADD BL,TJ       ;BL=BL+TJ（加上第二个数的第二位）（第二个数为一位数则是#，否则是二位数的个位）
    MOV J,BL        ;列数J=BL=第二个数,保留列数据

;初始化,防止数据遗留出问题    
    MOV TI,0        ;TI=0
    MOV TJ,0        ;TJ=0

;获得相对MP的8位/16位的偏移量:行数*15+列数才是当前元素的相对于MP的偏移量
    MOV AL,I                ;AL=I   
    MOV CL,15               ;CL=15
    MUL CL                  ;AL=AL*CL=AL*15 行数*15

    MOV CNTW,AX             ;CNTW=AX
    MOV DL,BYTE PTR CNTW    ;DL=CNTW
    ADD DL,J                ;DL=DL+J    行数*15+列数

    MOV CNT,DL              ;CNT=DL,保留相对于MP的偏移量 为byte
    MOV AX,WORD PTR CNT     ;AX=CNT
    MOV CNTW,AX             ;CNTW=AX，保留相对于MP的偏移量 为Word
    MOV BX,OFFSET MP        ;BX=MP的偏移量
    ADD BX,CNTW             ;BX=BX+CNTW
    MOV AL,[BX]             ;取到MP当前玩家准备落子的坐标

    CMP AL,0                ;AL和0比较，确认此处是否没有落子
    JNE folloB              ;AL不等于0表示此处有落子 该次落子失败,需要重新选择落子处，跳转到folloB
    
;成功落子,改变MP的坐标数据
    ADD OVERFLOW,1          ;棋盘中棋子数量+1
    MOV BYTE PTR[BX],12H    ;以W代表白棋  B代表黑棋（ASCII码42）        
                            ;因为棋盘输出时每个数字自动加30H转为字符，所以此处的B的ASCII码42先减去30H
;调用判断获胜与否的子程序   
    CALL WIN

    CMP OVERFLOW,225        ;比较棋子数量与225
    JE OVER_FLOW            ;OVER_FLOW=225时，棋盘满了，跳转到OVER_FLOW程序进行判断

    CMP FINISH,1            ;比较FINISH和1
    JE EXIT                 ;FINISH=1,获胜条件已达成，跳转到结束程序EXIT

    JMP NEAR PTR IN_WHITE   ;游戏没有达到结束条件，继续，跳转到IN_WHITE让白棋下

EXIT:    ;结束程序：棋局结束
;调用SHOW显示棋盘
    CALL SHOW

    MOV AL,NOW          ;AL=NOW，是获胜时落子的一方
    CMP AL,27H          ;AL和27H比较（W的ASCII码57-30）
    JE WHITE_WIN        ;AL=W，白方胜利，跳转到WHITE_WIN
   
    CMP AL,12H          ;AL和12H比较（B的ASCII码42-30）
    JE BLACK_WIN        ;AL=B,黑方胜利，跳转到BLACK_WIN

OVER_FLOW:  ;棋盘满了执行该程序
    CMP FINISH,1        ;比较FINISH和1
    JE WHITE_WIN        ;FINISH=1，即最后一子导致五子成线，白子先行，必是白棋落最后一子，所以白棋胜

;和棋
    MOV DX,OFFSET STR9  ;DX=STR9='Game ends,draw.',0DH,0AH,'$'
    MOV AH,9            ;AH=9
    INT 21h             ;执行中断，AH=9输出字符串DX，棋盘满了没人获胜，和棋

    JMP GAME_OVER       ;跳转到GAME_OVER

;白棋赢
WHITE_WIN:  
    MOV DX,OFFSET STR7  ;DX=STR7='White win.',0DH,0AH,'$'
    MOV AH,9            ;AH=9
    INT 21h             ;执行中断，AH=9，输出字符串DX，白棋获胜

    JMP GAME_OVER       ;跳转到GAME_OVER

;黑棋赢
BLACK_WIN:  
    MOV DX,OFFSET STR8  ;DX=STR8='Black win.',0DH,0AH,'$'
    MOV AH,9            ;AH=9
    INT 21h             ;执行中断，AH=9,输出字符串DX，黑棋获胜

    JMP GAME_OVER       ;跳转到GAME_OVER

;游戏结束    
GAME_OVER:
    MOV AX,4C00H    ;AX=4C00H
    INT 21H         ;中断，AH=4C00H时，AL=返回码，带返回码结束程序
MAIN ENDP   ;主程序MAIN结束

;获胜逻辑判断子程序
WIN PROC NEAR    
;初始化
    PUSH AX         ;AX入栈
    PUSH BX         ;BX入栈
    PUSH CX         ;CX入栈
    PUSH DX         ;DX入栈，保存现场

    MOV COUNT,1     ;COUNT=1,连续的棋子COUNT有1个
    MOV AL,[BX]     ;AL=BX寄存器中的值27H或12H（W或者B的ASCII码-30H的值）
    MOV NOW,AL      ;NOW=AL=27H或者12H，表示落子方W或者B        
    MOV CL,J        ;循环计数器CL=J，从左到右的判断 根据J的值判断是否退出循环

LEFT_TO_RIGHT_LEFT:;横向：向左判断         
;从左到右时count不刷新，进入新的一条线五子判断时才需要
    DEC CL                  ;CL-1，往左一个元素
    CMP CL,0                ;比较CL和0
    JB LEFT_TO_RIGHT_RIGHT  ;CL<0时，说明向左碰壁，跳转到LEFT_TO_RIGHT_RIGHT，进入向右判断

;左边还有棋子时执行
    SUB BX,1                ;BX地址减1，表示向左一个，BX是玩家选择的落子坐标的偏移地址，在主调用中给出
    CMP AL,[BX]             ;比较AL和BX中的值，BX是玩家选择的落子坐标的偏移地址
    JNE LEFT_TO_RIGHT_RIGHT ;AL≠BX中的值时，连接的棋子不同色，跳转到LEFT_TO_RIGHT_RIGHT
    ADD COUNT,1             ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1

    CMP COUNT,5             ;比较COUNT和5
    JE FIVE_TO_WINN         ;COUNT=5时，说明一方胜利，
    ;此处条件跳转可能超出 需要找个中继FIVE_TO_WINN继续跳转
    
    JMP LEFT_TO_RIGHT_LEFT  ;COUNT≠5时，循环LEFT_TO_RIGHT_LEFT继续向左比较

LEFT_TO_RIGHT_RIGHT:    ;横向：向右判断
;初始化
    MOV CL,J            ;CL=J=判断开始的点的列坐标
    MOV BX,OFFSET MP    ;重新定位到落子坐标,BX=MP的偏移地址
    ADD BX,CNTW         ;BX=BX+CNTW（MP的偏移量+相对于MP的偏移量CNTW）

START_1:    ;横向向右比较     
    INC CL              ;CL+1，往右一个元素
    CMP CL,14           ;CL和14比较
    JA UP_TIL_DOWN_UP   ;CL>14时，说明向右碰壁，跳转到UP_TIL_DOWN_UP纵向比较

;右边还有棋子时执行
    ADD BX,1            ;BX+1，表示向右一个
    CMP AL,[BX]         ;AL和BX中的值比较，BX是玩家选择的落子坐标的偏移地址
    JNE UP_TIL_DOWN_UP  ;AL≠BX中的值时，连接的棋子不同色，跳转到UP_TIL_DOWN_UP纵向比较
    ADD COUNT,1         ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1，

    CMP COUNT,5         ;COUNT和5比较
    JE FIVE_TO_WINN     ;COUNT=5，说明一方胜利，跳转到FIVE_TO_WINN
    JMP START_1         ;COUNT≠5时，循环START_1继续

UP_TIL_DOWN_UP:         ;纵向，向上判断
;初始化
    MOV CL,I            ;CL=I=行数
    MOV BX,OFFSET MP    ;BX=MP的偏移量，重新定位到落子坐标
    ADD BX,CNTW         ;BX=BX+CNTW（MP的偏移量+相对于MP的偏移量CNTW）
    MOV COUNT,1         ;进行纵向比较时要对count初始化

START_2:    ;纵向向上比较
    DEC CL              ;CL-1，表示向上一个
    CMP CL,0            ;比较CL和0
    JB UP_TIL_DOWN_DOWN ;CL<0时，表示向上碰壁，跳转到UP_TIL_DOWN_DOWN向下比较

;上面还有棋子时执行
    SUB BX,15           ;BX-15
    CMP AL,[BX]         ;比较AL和BX中的值，BX是玩家选择的落子坐标的偏移地址
    JNE UP_TIL_DOWN_DOWN;AL≠BX中的值时，连接的棋子不同色，跳转到UP_TIL_DOWN_DOWN向下比较
    ADD COUNT,1         ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1

    CMP COUNT,5         ;COUNT和5比较

FIVE_TO_WINN:       ;COUNT=5时或者在纵向向上比较时执行
    JE FIVE_TO_WIN  ;COUNT=5时跳转到FIVE_TO_WIN（除了纵向向上比较的COUNT一定等于5）
    JMP START_2     ;COUNT≠5时，循环START_2继续纵向向上比较

UP_TIL_DOWN_DOWN:   ;纵向向下比较
;初始化
    MOV CL,I        ;CL=I=最初的行数
    MOV BX,OFFSET MP    ;BX=MP的偏移量，重新定位到落子坐标
    ADD BX,CNTW         ;BX=BX+CNTW（MP的偏移量+相对于MP的偏移量CNTW）

START_3:    ;纵向向下比较
    INC CL              ;CL+1,表示往下一个元素
    CMP CL,14           ;比较CL和14
    JA LEFTUP_TO_RIGHTDOWN_LEFTUP   ;CL>14时，表示向下碰壁，跳转LEFTUP_TO_RIGHTDOWN_LEFTUP进行右下判断

    ADD BX,15           ;BX+15
    CMP AL,[BX]         ;AL和BX的值比较，BX是玩家选择的落子坐标的偏移地址
    JNE LEFTUP_TO_RIGHTDOWN_LEFTUP  ;AL≠BX中的值时，连接的棋子不同色，跳转进行右下判断
    ADD COUNT,1         ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1

    CMP COUNT,5         ;比较COUNT和5
    JE FIVE_TO_WIN      ;COUNT=5时，说明有一方胜利,跳转FIVE_TO_WIN
    JMP START_3         ;COUNT≠5时，循环START_3继续向下比较

LEFTUP_TO_RIGHTDOWN_LEFTUP:        ;左上到右下 向左上比较
;初始化
    MOV CL,I            ;CL=I=行数
    MOV DL,J            ;DL=J=列数
    MOV BX,OFFSET MP    ;BX=MP的偏移量，重新定位到落子坐标
    ADD BX,CNTW         ;BX=BX+CNTW（MP的偏移量+相对于MP的偏移量CNTW）
    MOV COUNT,1         ;进行左上右下比较时COUNT要初始化

START_4:    ;向左上比较
    DEC CL          ;CL-1,向上一个元素
    DEC DL          ;DL-1,向左一个元素
    CMP CL,0        ;比较CL和0
    JB LEFTUP_TO_RIGHTDOWN_RIGHTDOWN    ;CL<0时，说明向上碰壁，跳转到向右下比较

    CMP DL,0        ;比较DL和0
    JB LEFTUP_TO_RIGHTDOWN_RIGHTDOWN    ;DL<0时，说明向左碰壁，跳转到向右下比较

    SUB BX,16       ;BX-16
    CMP AL,[BX]     ;比较AL和BX中的值，BX是玩家选择的落子坐标的偏移地址    
    JNE LEFTUP_TO_RIGHTDOWN_RIGHTDOWN    ;AL≠BX中的值时，连接的棋子不同色，跳转到向右下比较
    ADD COUNT,1        ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1，
    CMP COUNT,5        ;COUNT和5比较

FIVE_TO_WIN:    ;COUNT=5或在向左上比较中执行
    JE FIVE_WINNN   ;COUNT=5，说明一方胜利，跳转到FIVE_TO_WINN
    JMP START_4     ;COUNT≠5，循环START_4继续向左上比较

LEFTUP_TO_RIGHTDOWN_RIGHTDOWN:    ;向右下比较
;初始化
    MOV CL,I            ;CL=I=行数
    MOV DL,J            ;DL=J=列数
    MOV BX,OFFSET MP    ;BX=MP的偏移量，重新定位到落子坐标
    ADD BX,CNTW         ;BX=BX+CNTW（MP的偏移量+相对于MP的偏移量CNTW）

START_5:    ;向右下比较
    INC CL      ;CL+1，向下一个元素
    INC DL      ;DL+1，向右一个元素
    CMP CL,14   ;比较CL和14
    JA LEFTDOWN_TO_RIGHTUP_LEFTDOWN ;CL>14时，说明向下碰壁，跳转到左下右上比较

    CMP DL,14   ;比较DL和14
    JA LEFTDOWN_TO_RIGHTUP_LEFTDOWN ;DL>14时，说明向右碰壁，跳转到左下右上比较

    ADD BX,16   ;BX+16
    CMP AL,[BX] ;比较AL和BX中的值，BX是玩家选择的落子坐标的偏移地址
    JNE LEFTDOWN_TO_RIGHTUP_LEFTDOWN    ;AL≠BX中的值时，连接的棋子不同色，跳转到左下右上比较
    ADD COUNT,1        ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1，
    CMP COUNT,5        ;COUNT和5比较

FIVE_WINNN:     ;COUNT=5时或者在左下右上比较时执行
    JE FIVE_WIN     ;COUNT=5，说明有一方胜利，跳转到FIVE_WIN
    JMP START_5     ;COUNT≠5，循环START_5继续向右下比较

LEFTDOWN_TO_RIGHTUP_LEFTDOWN:        ;左下右上比较 向左下部分
;初始化
    MOV CL,I    ;CL=I=行
    MOV DL,J    ;DL=J=列
    MOV BX,OFFSET MP    ;BX=MP的偏移量，重新定位到落子坐标
    ADD BX,CNTW         ;BX=BX+CNTW（MP的偏移量+相对于MP的偏移量CNTW）
    MOV COUNT,1         ;进行左下右上比较时COUNT要初始化

START_6:    ;向左下比较
    INC CL          ;CL+1，向下一个元素
    DEC DL          ;DL-1，向左一个元素
    CMP CL,14       ;CL和14比较
    JA LEFTDOWN_TO_RIGHTUP_RIGHTUP  ;CL>14时，表示向下碰壁，跳转到向右上比较
    
    CMP DL,0        ;DL和0比较
    JB LEFTDOWN_TO_RIGHTUP_RIGHTUP  ;DL<0时，表示向上碰壁，跳转到向右上比较

    ADD BX,14       ;BX+14
    CMP AL,[BX]     ;AL和BX中的值比较，BX是玩家选择的落子坐标的偏移地址
    JNE LEFTDOWN_TO_RIGHTUP_RIGHTUP ;AL≠BX中的值时，连接的棋子不同色，跳转到向右上比较

    ADD COUNT,1     ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1，
    CMP COUNT,5     ;COUNT和5比较
    JE FIVE_WIN     ;COUNT=5时，说明有一方胜利，跳转到FIVE_WIN    
    JMP START_6     ;COUNT≠5时，循环START_6继续向左下比较

LEFTDOWN_TO_RIGHTUP_RIGHTUP:    ;向右上比较
;初始化
    MOV CL,I            ;CL=I=行数
    MOV DL,J            ;DL=J=列数
    MOV BX,OFFSET MP    ;BX=MP的偏移量，重新定位到落子坐标
    ADD BX,CNTW         ;BX=BX+CNTW（MP的偏移量+相对于MP的偏移量CNTW）

START_7:    ;向右上比较        
    DEC CL          ;CL-1
    INC DL          ;DL+1
    CMP CL,0        ;比较CL和0
    JB OK           ;CL<0时，说明向上碰壁，结束获胜判断，跳转到OK

    CMP DL,14       ;比较DL和14
    JA OK           ;DL>14时，说明向右碰壁，结束获胜判断，跳转到OK

    SUB BX,14       ;BX+14
    CMP AL,[BX]     ;AL和BX中的值比较，BX是玩家选择的落子坐标的偏移地址
    JNE OK          ;AL≠BX中的值时，连接的棋子不同色，跳转到OK进行获胜判断
    ADD COUNT,1     ;AL=BX中的值时，即同色的棋子相连,一条线上的棋子数+1

    CMP COUNT,5     ;比较COUNT和5
    JE FIVE_WIN     ;COUNT=5时，说明有一方胜利，跳转到FIVE_WIN
    JMP START_7     ;COUNT≠5时，循环START_7继续向右上比较

FIVE_WIN:    ;出现了获胜的棋面 结束游戏并显示胜利者
    MOV FINISH,1    ;FINISH=1,作为胜利变量在FIVE_WINN中判断胜利

OK: ;结束胜负判断
;恢复现场
    MOV COUNT,1     ;COUNT=1
    POP DX          ;DX出栈
    POP CX          ;CX出栈
    POP BX          ;BX出栈
    POP AX          ;AX出栈
    RET             ;返回原调用程序
WIN ENDP    ;判断胜利的代码段结束

;屏幕显示棋盘子程序
SHOW PROC NEAR            
;初始化 
    PUSH AX                ;AX进栈
    PUSH BX                ;BX进栈
    PUSH CX                ;CX进栈
    PUSH DX                ;保存现场    
;显示棋盘部分
    MOV DX,OFFSET STR2     ;STR2的偏移量赋值给数据寄存器DX
    MOV AH,9               ;赋值AX的高八位为9（0000 1001）
    INT 21h                ;中断指令
    MOV BX,OFFSET MP       ;当前是第几个元素   MP的偏移量赋值给基址寄存器BX
    MOV CH,0               ;行数为0，赋值给计数寄存器CX的高八位（0000 0000）

NICE:    ;前10行的行号
    MOV CL,0               ;列数为0，赋值给计数寄存器CX的低八位（0000 0000）
    CMP CH,10              ;行数大于等于10不用加空格前缀   CH与10进行比较
    JAE UPTEN              ;CH大于等于10时转移到UPTEN   
;CH小于10时执行 
;输出空格
    MOV AH,02H             ;赋值给AX的高八位02（0000 0010）
    MOV DL,' '             ;字符前缀
    INT 21H                ;中断指令   AH=2时，INT21H显示输出，输出DL的值：一个空格

;输出行号
    MOV AH,02H             ;赋值给AX的高八位02（0000 0010）
    MOV DL, CH             ;循环计数器CH赋值给寄存器DX的低八位
    ADD DL,30H             ;DL+30H ASCII字符'0'到'9'的十六进制表示为30H到39H。
                        ;DL=31H,即DL为ASCII的字符'1'，以此类推
    INT 21H                ;中断 AH=2时，INT21H显示输出，输出DL的值：第m次循环就输出m-1（m-1从0开始）
;输出两个空格 
    MOV AH,02H             ;赋值AX的低八位为02H（0000 0010）
    MOV DL, ' '            ;字符间隔
    INT 21H                ;中断 AH=2时，INT21H显示输出，输出DL的值：一个空格   
    MOV AH,02H             ;赋值AX的低八位为02H（0000 0010）
    MOV DL, ' '            ;字符间隔 
    INT 21H                ;中断 AH=2时，INT21H显示输出，输出DL的值：一个空格

    JMP LINE               ;跳转到LINE：给一行的15个元素赋值
    
UPTEN: ;第11行到第15行的行号（10——14）
;输出行号的十位
    MOV AH,02H     ;赋值AH为02
    MOV DL,'1'     ;十位    10、11、12、13、14的十位都是1
    INT 21H        ;中断，AH为02的时候输出DL的值：1

;输出行号的个位
    MOV AH,02H     ;赋值AH为02
    MOV DL,CH      ;将循环计数器CH的值赋给DL
    ADD DL,38      ;个位 DL=0，1,2,3,4,5（循环次数）
    INT 21H        ;中断，AH为02时输出DL的值：0，1,2,3,4,5

;输出两个空格
    MOV AH,02H     ;赋值AX的低八位为02H（0000 0010）
    MOV DL, ' '    ;字符间隔
    INT 21H        ;中断 AH=2时，INT21H显示输出，输出DL的值：一个空格
    MOV AH,02H     ;赋值AX的低八位为02H（0000 0010）
    MOV DL, ' '    ;字符间隔调整
    INT 21H        ;中断 AH=2时，INT21H显示输出，输出DL的值：一个空格

LINE:      ;给一行15个元素赋值
;输出寄存器BX中的值
    MOV AH, 02H    ;AH赋值02
    MOV DL,[BX]    ;DL赋值为基址寄存器BX中的值（当前第几个元素）
    ADD DL,30H     ;DL+30H
    INT 21H        ;中断，AH=02时输出DL中的值

;输出两个空格
    MOV AH,02H     ;AH赋值02
    MOV DL, ' '    ;DL赋值为一个空格
    INT 21H        ;中断，AH=02时输出DL中的值     
    MOV AH,02H     ;AH赋值02
    MOV DL, ' '    ;DL赋值为一个空格
    INT 21H        ;中断，AH=02时输出DL中的值

    ADD BX,1       ;基址寄存器BX+1，对应后一个元素
    ADD CL,1       ;CL+1
    CMP CL,15      ;CL与15比较
    JB LINE        ;若CL小于15，则当前行未到尾部，循环LINE，继续输出（14列）

;当CL>=15时该行已到尾，执行换行
;0AH=换行,0DH=回车,一起执行=光标向下移动一列并回到该列最前位置
    MOV DL,0DH     ;DL=回车符（回车符的ASCII码为0DH）
    MOV AH,2       ;赋值AH=02
    INT 21H        ;中断，AH=02的时候输出DL的值

    MOV DL,0AH     ;DL=换行符号（ASCII码为0AH）
    MOV AH,2       ;赋值AH=02   
    INT 21H        ;中断，AH=02的时候输出DL的值

    ADD CH,1       ;显示当前行  自增到下一行
    CMP CH,15      ;CH小于15的时候，还没到第15行，继续循环
    JAE FIN        ;CH大于等于15时，棋盘显示完毕，跳转FIN（结束程序）    
    JMP NICE       ;跳转到NICE：从行号开始初始化棋盘

FIN:    ;结束棋盘输出,恢复现场
    POP DX         ;DX出栈
    POP CX         ;CX出栈
    POP BX         ;BX出栈
    POP AX         ;AX出栈
    RET            ;返回原调用程序
SHOW ENDP   ;输出棋盘的代码段结束
CSEG ENDS   ;代码段CSEG结束
END START   ;START代码段结束